package com.kingstar.ceres.data.facade;

import com.alibaba.fastjson.JSONObject;
import com.kingstar.ceres.data.bo.BaseFileMapping;
import com.kingstar.ceres.data.bo.FileImportBO;
import com.kingstar.ceres.data.constant.FileImportConstants;
import com.kingstar.ceres.data.dto.FileImportDTO;
import com.kingstar.ceres.data.function.TriConsumer;
import com.kingstar.ceres.data.handler.ITaskLockHandler;
import com.kingstar.ceres.data.load.ResourceLoad;
import com.kingstar.ceres.data.remote.ConnectionFactory;
import com.kingstar.ceres.data.remote.ConnectionWrapper;
import com.kingstar.ceres.data.strategy.DataInitFactory;
import com.kingstar.ceres.data.util.BeanConvertUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;

import javax.annotation.Resource;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
public abstract class AbstractDataInitFacade implements IDataInitFacade {

    protected ITaskLockHandler taskLockHandler;

    protected ConnectionFactory connectionFactory;

    @Resource
    DataInitFactory dataInitFactory;

    @Resource
    ResourceLoad resourceLoad;

    @Override
    public boolean execute(String param,String fileType) throws Exception {
        //1.执行参数准备
        FileImportDTO dto = prepareParam(param);
        //2.文件清单准备
        FileImportBO bo = prepareFileDetailed(dto,fileType);
        //3.检查是否有ok文件
        checkFileComplete(bo);
        //4.判断是否可执行
        if (!checkTaskStatusAndTrans2Doing(bo)){
            log.info("任务正在执行中,或已完成:{}",param);
            return Boolean.TRUE;
        }
        //5.多线程处理文件
        boolean fileProcessResult = fileDataProcess(bo);
        //6.文件导入完成后处理
        afterProcess(fileProcessResult);
        return fileProcessResult;
    }

    protected FileImportDTO prepareParam(String param) {
        if (StringUtils.isNotBlank(param)){
            log.info("begin abstract-data-facade prepare-param:{}",param);
            FileImportDTO dto = JSONObject.parseObject(param, FileImportDTO.class);
            dto.setMode(FileImportConstants.MOD_MANUAL);
            String opDate = dto.getOpDate();
            if (StringUtils.isBlank(opDate)){
                dto.setOpDate(LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE));
            }
            String operationType = dto.getOperationType();
            if (StringUtils.isBlank(operationType)){
                dto.setMode(FileImportConstants.MOD_AUTO);
                dto.setOperationType(FileImportConstants.OP_TYPE_RETRY);
            }
            log.info("end abstract-data-facade prepare-param:{}",dto);
            return dto;
        }
        FileImportDTO dto=new FileImportDTO();
        dto.setMode(FileImportConstants.MOD_AUTO);
        dto.setOpDate(LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE));
        log.info("abstract-data-facade prepare-no-param:{}",dto);
        return dto;
    }

    protected FileImportBO prepareFileDetailed(FileImportDTO dto,String fileType) throws Exception {
        FileImportBO bo = BeanConvertUtils.INSTANCE.fileImportDTO2BO(dto);
        List<? extends BaseFileMapping> fileMapping =
                resourceLoad.loadConfig(dto.getFileIds(), fileType, bo.getBatchPath());
        bo.setFileMappings(fileMapping);
        bo.setTaskId(fileType);
        return bo;
    }

    protected boolean checkTaskStatusAndTrans2Doing(FileImportBO bo) {
        log.info("check-task-status,param:{}",bo);
        String operationType = bo.getOperationType();
        //1.强制执行时重置任务状态
        if (Objects.equals(FileImportConstants.OP_TYPE_FORCE,operationType)){
            log.info("check-task-status,operationType:{}",operationType);
            List<? extends BaseFileMapping> fileMappings = bo.getFileMappings();
            List<String> fileIds=null;
            if (fileMappings!=null && !fileMappings.isEmpty()){
                fileIds=fileMappings.stream().map(v->v.getFileId()).collect(Collectors.toList());
                fileIds.add(bo.getTaskId());
            }
            taskLockHandler.resetTaskStatus(bo.getTaskId(), fileIds,bo.getOpDate(),FileImportConstants.STATUS_UNDO);
        }
        //2.判断任务是否可执行
        return taskLockHandler.checkTaskStatusAndTransToDoing(
                bo.getOpDate(), bo.getTaskId(), bo.getTaskId(),bo.getMode()
        );
    }

    protected boolean doProcess(FileImportBO bo, int coreSize , String type, TriConsumer<BaseFileMapping,String,String> consumer) throws Exception {
        log.info("begin {}-data-process:{}",type,bo);
        //1.构建线程池
        List<? extends BaseFileMapping> entities = bo.getFileMappings();
        int taskCount = entities.size();
        StopWatch watch=new StopWatch();
        watch.start();
        CountDownLatch countDownLatch = new CountDownLatch(taskCount);
        int poolSize=taskCount;
        if (coreSize!=0){
            poolSize=coreSize;
        }
        ThreadPoolExecutor executor = new ThreadPoolExecutor(poolSize, poolSize,
                3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(taskCount),
                new ThreadPoolExecutor.CallerRunsPolicy());
        //2.提交任务
        for (BaseFileMapping entity : entities) {
            executor.execute(()->{
                consumer.accept(entity, bo.getOpDate(), bo.getMode());
                countDownLatch.countDown();
            });
        }
        log.info("submit {}-data-process:{}",type,bo);
        countDownLatch.await();
        executor.shutdown();
        log.info("end {}-data-process, all file deal complete,total time:{}ms",type,watch.getTime());
        watch.stop();
        //3.任务执行后提交任务状态
        return taskLockHandler.taskComplete(bo.getTaskId(), bo.getOpDate());
    }

    protected void register(String fileType,IDataInitFacade dataInitFacade){
        dataInitFactory.register(fileType,dataInitFacade);
    }

    protected abstract void checkFileComplete(FileImportBO bo);

    public boolean downloadFile(FileImportBO bo){
        //todo check ok 处理正则文件名
        final Map<String, ? extends List<? extends BaseFileMapping>> map = bo.getFileMappings().stream()
                .filter(v -> StringUtils.isNotBlank(v.getConnect()))
                .collect(Collectors.groupingBy(BaseFileMapping::getConnect));
        if(map.size()==0){
            return true;
        }
        for (Map.Entry<String, ? extends List<? extends BaseFileMapping>> entry : map.entrySet()) {
            final ConnectionWrapper wrapper = connectionFactory.fetchConnection(entry.getKey());
            if(!wrapper.connection()){
                wrapper.close();
                return false;
            }
            for (BaseFileMapping mapping:entry.getValue()){
                //文件已存在并且不是强制执行
                final File file = new File(mapping.getLocalPath());
                if(!file.exists()){
                    file.mkdirs();
                }
                if(isDownload(mapping.getLocalPath(),mapping.getFileName())
                        && !FileImportConstants.OP_TYPE_FORCE.equals(bo.getOperationType())){
                    log.info("文件已下载:{}",mapping.getFileName());
                    continue;
                }

                if (!wrapper.download(wrapper.buildRemotePath(mapping.getRemotePath(), mapping.getFileName()),
                        Paths.get(mapping.getLocalPath(),mapping.getFileName()).toString())){
                    wrapper.close();
                    return false;
                }
            }
            wrapper.close();
        }
        return true;
    }

    private boolean isDownload(String dir,String filename){
        final Path path = Paths.get(dir, filename);
        return Files.exists(path);
    }

    protected void afterProcess(boolean fileProcessResult){
        if (!fileProcessResult){
            log.warn("file process failed");
            return;
        }
        afterSuccess();
    }
    protected void afterSuccess(){

    }

    protected void checkFileExist(Path path){
        if (!Files.exists(path)){
            log.error("file is no ok,path:{}",path);
            throw new RuntimeException("file is no ok: "+path);
        }
    }
}
